/*
 * Copyright © 2021 http://www.hn-szzy.com/ All rights reserved.
 */
package com.xjh.utils.aspect;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xjh.bo.LogBO;
import com.xjh.service.impl.LogServiceImpl;
import com.xjh.utils.annotation.LogTest;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * @author： 徐建华
 * @date： 2022/1/3
 * @description：配置日志，通过自定义注解实现
 */
//定义为切面类
@Aspect
//注入spring
@Component
public class LogAspect {

  @Autowired
  private LogServiceImpl logServiceImpl;
  private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

  // 配置织入点（织入点就是所要切入的内容）,这里切入的是个注解，代表有该注解的地方执行这个切入方法
  @Pointcut("@annotation(com.xjh.utils.annotation.LogTest)")
  public void logPointCut() {
  }

  /**
   * 最终通知：处理完请求后执行
   *
   * @param joinPoint 切点
   */
//    切点就是织入的方法具体在什么时候执行，这里定义为处理完请求后执行(方法执行完后，添加日志)
  @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
  public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
    handleLog(joinPoint, null, jsonResult);
  }

  /**
   * 异常通知：如果处理请求时出现异常，在抛出异常后执行此处代码，注意是请求的方法！！！
   *
   * @param joinPoint 切点
   * @param e         异常
   */
  @AfterThrowing(pointcut = "logPointCut()", throwing = "e")
  public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
    handleLog(joinPoint, e, null);
  }


  protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
    try {
      // 获得注解
      LogTest controllerLog = getAnnotationLog(joinPoint);
      if (controllerLog == null) {
        return;
      }
      // *========数据库日志=========*//
      LogBO operLog = new LogBO();
      //操作成功
      operLog.setStatus(0);
      logServiceImpl.insert(operLog);
      // 获取用户请求的参数集合
      String serviceMthodParams = getServiceMthodParams(joinPoint);
      // 最好使用实体类型，且只包含一个实体比如[{age:23,name:"xujianhua"}],那么取出参数的时候就很容易，可以转成json格式
      System.out.println("获取用户传的参数(impl方法中的参数)===" + serviceMthodParams);
      // todo 获取方法中第一个参数，转成json，并取出json中的第一个参数
      JSONArray params = JSON.parseArray(serviceMthodParams);

      Object o = params.get(0);
      // 如果参数不是‘{}’类型，使用JSON.parseObject会报错
      JSONObject jsonObject = JSON.parseObject(o.toString());
      if (!Objects.isNull(jsonObject.get("hitCount")) ) {
        System.out.println("hitCount===" + jsonObject.get("hitCount"));
      }

      // 获取注解中的描述信息
      String context = getServiceMthodDescription(joinPoint);
      System.out.println("描述信息===" + context);

      // request相关

      HttpServletRequest httpServletRequest = getHttpServletRequest();

      //设置ip地址
      System.out.println(httpServletRequest.getRemoteAddr());
      operLog.setOperIp(httpServletRequest.getRemoteAddr());

      //设置请求地址 ,比如/api/student/queryAll
      System.out.println(httpServletRequest.getRequestURI());
      operLog.setOperUrl(httpServletRequest.getRequestURI());
      //注解所在方法的 返回值 判断是为了最终通知和异常通知都能执行
      if (!Objects.isNull(jsonResult)) {
        System.out.println("jsonResult===" + jsonResult);
      }

      // 异常信息 判断是为了最终通知和异常通知都能执行

      if (!Objects.isNull(e)) {
        System.out.println("异常信息===" + e.getMessage());
      }


    } catch (Exception exp) {
      // 记录本地异常日志
      log.error("==最终通知异常==");
      log.error("异常信息:{}", exp.getMessage());
      exp.printStackTrace();

    }


  }


  /**
   * @Author: XuJianHua
   * @Date 2022/11/3 16:01
   * @Description: 抽取出判断注解是否存在的方法, 是否存在注解，如果存在就获取
   **/

  private LogTest getAnnotationLog(JoinPoint joinPoint) throws Exception {
    Signature signature = joinPoint.getSignature();
    MethodSignature methodSignature = (MethodSignature) signature;
    Method method = methodSignature.getMethod();

    if (method != null) {
      return method.getAnnotation(LogTest.class);
    }
    return null;
  }

  /**
   * 获取json格式的参数用于存储到数据库中
   * 如果参数是实体类型，会带参数名，比如：[{"records":[],"total":0,"size":10,"current":10,"orders":[],"optimizeCountSql":true,"hitCount":false,"searchCount":true,"pages":0}]
   * 如果不是实体类型则不会带，比如四个参数：[{},1,5,null]
   *
   * @param joinPoint
   * @return
   * @throws Exception
   */
  private String getServiceMthodParams(JoinPoint joinPoint) throws Exception {
    Object[] arguments = joinPoint.getArgs();
    ObjectMapper om = new ObjectMapper();
    return om.writeValueAsString(arguments);
  }


  /**
   * 获取自定义注解里的日志描述
   *
   * @param joinPoint
   * @return 返回注解里面的日志描述
   * @throws Exception
   */
  private String getServiceMthodDescription(JoinPoint joinPoint)
      throws Exception {
    //类名
    String targetName = joinPoint.getTarget().getClass().getName();
    //方法名
    String methodName = joinPoint.getSignature().getName();
    //参数
    Object[] arguments = joinPoint.getArgs();
    //通过反射获取示例对象
    Class targetClass = Class.forName(targetName);
    //通过实例对象方法数组
    Method[] methods = targetClass.getMethods();
    String description = "";
    for (Method method : methods) {
      //判断方法名是不是一样
      if (method.getName().equals(methodName)) {
        //对比参数数组的长度
        Class[] clazzs = method.getParameterTypes();
        if (clazzs.length == arguments.length) {
          //获取注解里的日志信息
          description = method.getAnnotation(LogTest.class).description();
          break;
        }
      }
    }
    return description;
  }

  /**
   * @Author: XuJianHua
   * @Date 2022/11/4 0:25
   * @Description: 获取request中相关信息
   **/
  public HttpServletRequest getHttpServletRequest() {
    RequestAttributes ra = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes sra = (ServletRequestAttributes) ra;
    HttpServletRequest request = sra.getRequest();
    return request;
  }


}
